﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;
using NHibernate.Criterion;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IAttributeHolderRepository<TModel, TStatistics> : IRepository<TModel, TStatistics>
        where TModel : AttributeHolder where TStatistics : IStatistics
    {
        IEnumerable<string> SearchAttribute(Guid id, string text, AttributeType type);
    }

    public abstract class AttributeHolderRepository<TModel, TStatistics, TError> : GeneralRepositoryBase<TModel, TStatistics, TError>, IAttributeHolderRepository<TModel, TStatistics>
        where TModel : AttributeHolder 
        where TStatistics : IStatistics
    {
        protected AttributeHolderRepository(IRepositoryConfiguration repositoryConfiguration, ISession session) 
            : base(repositoryConfiguration, session)
        {
        }

        private static ICriteria AddAttributeSearchCriteria(ICriteria criteria, Dictionary<Attribute, string> attributeMap, string tableAlias)
        {
            if (attributeMap != null && attributeMap.Any())
            {
                foreach (KeyValuePair<Attribute, string> attribute in attributeMap)
                {
                    DetachedCriteria detachedCriteria = DetachedCriteria.For<AttributeValue>("av")
                        .SetProjection(Projections.Property<AttributeValue>(x => x.Id))
                        .Add(Restrictions.EqProperty(tableAlias + ".Id", Projections.Property<AttributeValue>(x => x.AttributeHolder.Id)))
                        .Add(Restrictions.Eq(Projections.Property<AttributeValue>(x => x.Attribute.Id), attribute.Key.Id))
                        .Add(Restrictions.InsensitiveLike(Projections.Property<AttributeValue>(x => x.Value), "%" + attribute.Value + "%"));
                    criteria = criteria.Add(Subqueries.Exists(detachedCriteria));
                }
            }
            return criteria;
        }

        protected ICriteria CreateCriteria(Dictionary<Attribute, string> attributeMap, string tableAlias)
        {
            ICriteria criteria = Session.CreateCriteria<TModel>(tableAlias)
                .Add(Restrictions.Eq(Projections.Property<TModel>(x => x.Representante.Id), GetRepresentanteId()));

            criteria = AddAttributeSearchCriteria(criteria, attributeMap, tableAlias);

            return criteria;
        }

        public IEnumerable<string> SearchAttribute(Guid id, string text, AttributeType type)
        {
            return Session.QueryOver<AttributeValue>()
                .Where(Restrictions.InsensitiveLike(Projections.Property<AttributeValue>(x => x.Value), "%" + text.ToLower() + "%"))
                .Inner.JoinQueryOver(x => x.Attribute)
                .And(x => x.Representante.Id == GetRepresentanteId())
                .And(x => x.Id == id)
                .And(x => x.Type == type)
                .Select(x => x.Value)
                .Future<string>()
                .Distinct();
        }

        protected sealed override IQueryOver<TModel, TModel> GetModelsQueryOver()
        {
            IQueryOver<TModel, TModel> queryOver = Session.QueryOver<TModel>()
                .Where(x => x.Representante.Id == GetRepresentanteId());
            return GetModelsQueryOver(queryOver);
        }

        protected abstract IQueryOver<TModel, TModel> GetModelsQueryOver(IQueryOver<TModel, TModel> queryOver);

        protected sealed override IQueryOver<TModel, TModel> GetModelByIdQueryOver(IQueryOver<TModel, TModel> queryOver, Guid id)
        {
            queryOver = queryOver.And(x => x.Representante.Id == GetRepresentanteId());
            return DoGetModelByIdQueryOver(queryOver, id);
        }

        protected abstract IQueryOver<TModel, TModel> DoGetModelByIdQueryOver(IQueryOver<TModel, TModel> queryOver, Guid id);

        protected sealed override IEnumerable<Expression<Func<TModel, bool>>> GetUniquenessExpressions(TModel model)
        {
            List<Expression<Func<TModel, bool>>> result = new List<Expression<Func<TModel, bool>>>
            {
                x => x.Representante.Id == GetRepresentanteId()
            };
            result.AddRange(DoGetUniquenessExpressions(model));
            return result;
        }

        protected abstract IEnumerable<Expression<Func<TModel, bool>>> DoGetUniquenessExpressions(TModel model);

        protected sealed override void OnBeforeRemove(TModel model)
        {
            if (model.Representante.Id != GetRepresentanteId())
            {
                ThrowRepositoryException(RepresentanteMissmatchError);
            }
            model.ClearAttributeValues();
            DoOnBeforeRemove(model);
        }

        protected abstract TError RepresentanteMissmatchError { get; }

        protected virtual void DoOnBeforeRemove(TModel model)
        {
        }

        public override IEnumerable<TModel> GetModelsByIds(IEnumerable<Guid> ids)
        {
            return Session.QueryOver<TModel>()
                .Where(x => x.Representante.Id == GetRepresentanteId())
                .And(x => x.Id.IsIn(ids.ToArray()))
                .List();
        }
    }
}